// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/memory/memory_pressure_monitor_win.h"

#include "base/macros.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/message_loop/message_loop.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {
namespace win {

    namespace {

        struct PressureSettings {
            int phys_left_mb;
            MemoryPressureListener::MemoryPressureLevel level;
        };

    } // namespace

    // This is outside of the anonymous namespace so that it can be seen as a friend
    // to the monitor class.
    class TestMemoryPressureMonitor : public MemoryPressureMonitor {
    public:
        using MemoryPressureMonitor::CalculateCurrentPressureLevel;
        using MemoryPressureMonitor::CheckMemoryPressure;

        static const DWORDLONG kMBBytes = 1024 * 1024;

        explicit TestMemoryPressureMonitor(bool large_memory)
            : mem_status_()
        {
            // Generate a plausible amount of memory.
            mem_status_.ullTotalPhys = static_cast<DWORDLONG>(GenerateTotalMemoryMb(large_memory)) * kMBBytes;

            // Rerun InferThresholds using the test fixture's GetSystemMemoryStatus.
            InferThresholds();
            // Stop the timer.
            StopObserving();
        }

        TestMemoryPressureMonitor(int system_memory_mb,
            int moderate_threshold_mb,
            int critical_threshold_mb)
            : MemoryPressureMonitor(moderate_threshold_mb, critical_threshold_mb)
            , mem_status_()
        {
            // Set the amount of system memory.
            mem_status_.ullTotalPhys = static_cast<DWORDLONG>(
                system_memory_mb * kMBBytes);

            // Stop the timer.
            StopObserving();
        }

        virtual ~TestMemoryPressureMonitor() { }

        MOCK_METHOD1(OnMemoryPressure,
            void(MemoryPressureListener::MemoryPressureLevel level));

        // Generates an amount of total memory that is consistent with the requested
        // memory model.
        int GenerateTotalMemoryMb(bool large_memory)
        {
            int total_mb = 64;
            while (total_mb < MemoryPressureMonitor::kLargeMemoryThresholdMb)
                total_mb *= 2;
            if (large_memory)
                return total_mb * 2;
            return total_mb / 2;
        }

        // Sets up the memory status to reflect the provided absolute memory left.
        void SetMemoryFree(int phys_left_mb)
        {
            // ullTotalPhys is set in the constructor and not modified.

            // Set the amount of available memory.
            mem_status_.ullAvailPhys = static_cast<DWORDLONG>(phys_left_mb) * kMBBytes;
            DCHECK_LT(mem_status_.ullAvailPhys, mem_status_.ullTotalPhys);

            // These fields are unused.
            mem_status_.dwMemoryLoad = 0;
            mem_status_.ullTotalPageFile = 0;
            mem_status_.ullAvailPageFile = 0;
            mem_status_.ullTotalVirtual = 0;
            mem_status_.ullAvailVirtual = 0;
        }

        void SetNone()
        {
            SetMemoryFree(moderate_threshold_mb() + 1);
        }

        void SetModerate()
        {
            SetMemoryFree(moderate_threshold_mb() - 1);
        }

        void SetCritical()
        {
            SetMemoryFree(critical_threshold_mb() - 1);
        }

    private:
        bool GetSystemMemoryStatus(MEMORYSTATUSEX* mem_status) override
        {
            // Simply copy the memory status set by the test fixture.
            *mem_status = mem_status_;
            return true;
        }

        MEMORYSTATUSEX mem_status_;

        DISALLOW_COPY_AND_ASSIGN(TestMemoryPressureMonitor);
    };

    class WinMemoryPressureMonitorTest : public testing::Test {
    protected:
        void CalculateCurrentMemoryPressureLevelTest(
            TestMemoryPressureMonitor* monitor)
        {

            int mod = monitor->moderate_threshold_mb();
            monitor->SetMemoryFree(mod + 1);
            EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
                monitor->CalculateCurrentPressureLevel());

            monitor->SetMemoryFree(mod);
            EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
                monitor->CalculateCurrentPressureLevel());

            monitor->SetMemoryFree(mod - 1);
            EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
                monitor->CalculateCurrentPressureLevel());

            int crit = monitor->critical_threshold_mb();
            monitor->SetMemoryFree(crit + 1);
            EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
                monitor->CalculateCurrentPressureLevel());

            monitor->SetMemoryFree(crit);
            EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
                monitor->CalculateCurrentPressureLevel());

            monitor->SetMemoryFree(crit - 1);
            EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
                monitor->CalculateCurrentPressureLevel());
        }

        base::MessageLoopForUI message_loop_;
    };

    // Tests the fundamental direct calculation of memory pressure with automatic
    // small-memory thresholds.
    TEST_F(WinMemoryPressureMonitorTest, CalculateCurrentMemoryPressureLevelSmall)
    {
        static const int kModerateMb = MemoryPressureMonitor::kSmallMemoryDefaultModerateThresholdMb;
        static const int kCriticalMb = MemoryPressureMonitor::kSmallMemoryDefaultCriticalThresholdMb;

        TestMemoryPressureMonitor monitor(false); // Small-memory model.

        EXPECT_EQ(kModerateMb, monitor.moderate_threshold_mb());
        EXPECT_EQ(kCriticalMb, monitor.critical_threshold_mb());

        ASSERT_NO_FATAL_FAILURE(CalculateCurrentMemoryPressureLevelTest(&monitor));
    }

    // Tests the fundamental direct calculation of memory pressure with automatic
    // large-memory thresholds.
    TEST_F(WinMemoryPressureMonitorTest, CalculateCurrentMemoryPressureLevelLarge)
    {
        static const int kModerateMb = MemoryPressureMonitor::kLargeMemoryDefaultModerateThresholdMb;
        static const int kCriticalMb = MemoryPressureMonitor::kLargeMemoryDefaultCriticalThresholdMb;

        TestMemoryPressureMonitor monitor(true); // Large-memory model.

        EXPECT_EQ(kModerateMb, monitor.moderate_threshold_mb());
        EXPECT_EQ(kCriticalMb, monitor.critical_threshold_mb());

        ASSERT_NO_FATAL_FAILURE(CalculateCurrentMemoryPressureLevelTest(&monitor));
    }

    // Tests the fundamental direct calculation of memory pressure with manually
    // specified threshold levels.
    TEST_F(WinMemoryPressureMonitorTest,
        CalculateCurrentMemoryPressureLevelCustom)
    {
        static const int kSystemMb = 512;
        static const int kModerateMb = 256;
        static const int kCriticalMb = 128;

        TestMemoryPressureMonitor monitor(kSystemMb, kModerateMb, kCriticalMb);

        EXPECT_EQ(kModerateMb, monitor.moderate_threshold_mb());
        EXPECT_EQ(kCriticalMb, monitor.critical_threshold_mb());

        ASSERT_NO_FATAL_FAILURE(CalculateCurrentMemoryPressureLevelTest(&monitor));
    }

    // This test tests the various transition states from memory pressure, looking
    // for the correct behavior on event reposting as well as state updates.
    TEST_F(WinMemoryPressureMonitorTest, CheckMemoryPressure)
    {
        // Large-memory.
        testing::StrictMock<TestMemoryPressureMonitor> monitor(true);
        MemoryPressureListener listener(
            base::Bind(&TestMemoryPressureMonitor::OnMemoryPressure,
                base::Unretained(&monitor)));

        // Checking the memory pressure at 0% load should not produce any
        // events.
        monitor.SetNone();
        monitor.CheckMemoryPressure();
        message_loop_.RunUntilIdle();
        EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
            monitor.GetCurrentPressureLevel());

        // Setting the memory level to 80% should produce a moderate pressure level.
        EXPECT_CALL(monitor,
            OnMemoryPressure(MemoryPressureListener::
                    MEMORY_PRESSURE_LEVEL_MODERATE));
        monitor.SetModerate();
        monitor.CheckMemoryPressure();
        message_loop_.RunUntilIdle();
        EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
            monitor.GetCurrentPressureLevel());
        testing::Mock::VerifyAndClearExpectations(&monitor);

        // Check that the event gets reposted after a while.
        for (int i = 0; i < monitor.kModeratePressureCooldownCycles; ++i) {
            if (i + 1 == monitor.kModeratePressureCooldownCycles) {
                EXPECT_CALL(monitor,
                    OnMemoryPressure(MemoryPressureListener::
                            MEMORY_PRESSURE_LEVEL_MODERATE));
            }
            monitor.CheckMemoryPressure();
            message_loop_.RunUntilIdle();
            EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
                monitor.GetCurrentPressureLevel());
            testing::Mock::VerifyAndClearExpectations(&monitor);
        }

        // Setting the memory usage to 99% should produce critical levels.
        EXPECT_CALL(monitor,
            OnMemoryPressure(MemoryPressureListener::
                    MEMORY_PRESSURE_LEVEL_CRITICAL));
        monitor.SetCritical();
        monitor.CheckMemoryPressure();
        message_loop_.RunUntilIdle();
        EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
            monitor.GetCurrentPressureLevel());
        testing::Mock::VerifyAndClearExpectations(&monitor);

        // Calling it again should immediately produce a second call.
        EXPECT_CALL(monitor,
            OnMemoryPressure(MemoryPressureListener::
                    MEMORY_PRESSURE_LEVEL_CRITICAL));
        monitor.CheckMemoryPressure();
        message_loop_.RunUntilIdle();
        EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
            monitor.GetCurrentPressureLevel());
        testing::Mock::VerifyAndClearExpectations(&monitor);

        // When lowering the pressure again there should be a notification and the
        // pressure should go back to moderate.
        EXPECT_CALL(monitor,
            OnMemoryPressure(MemoryPressureListener::
                    MEMORY_PRESSURE_LEVEL_MODERATE));
        monitor.SetModerate();
        monitor.CheckMemoryPressure();
        message_loop_.RunUntilIdle();
        EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
            monitor.GetCurrentPressureLevel());
        testing::Mock::VerifyAndClearExpectations(&monitor);

        // Check that the event gets reposted after a while.
        for (int i = 0; i < monitor.kModeratePressureCooldownCycles; ++i) {
            if (i + 1 == monitor.kModeratePressureCooldownCycles) {
                EXPECT_CALL(monitor,
                    OnMemoryPressure(MemoryPressureListener::
                            MEMORY_PRESSURE_LEVEL_MODERATE));
            }
            monitor.CheckMemoryPressure();
            message_loop_.RunUntilIdle();
            EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
                monitor.GetCurrentPressureLevel());
            testing::Mock::VerifyAndClearExpectations(&monitor);
        }

        // Going down to no pressure should not produce an notification.
        monitor.SetNone();
        monitor.CheckMemoryPressure();
        message_loop_.RunUntilIdle();
        EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
            monitor.GetCurrentPressureLevel());
        testing::Mock::VerifyAndClearExpectations(&monitor);
    }

} // namespace win
} // namespace base
